// --------------------------------
// <copyright file="GameManager.cs" company="Faculty of Information Technology, Czech Technical University in Prague">
//     This document is shared by Microsoft Public License (MS-PL)
//     For further details please see http://msdn.microsoft.com/en-us/library/ff648068.aspx or license.txt.
// </copyright>
// <author>Vladislav Richter</author>

// <email>richtvl2@fit.cvut.cz</email>

// <date>9. 11. 2011 22:22:37</date>

// <summary>
//
// </summary>
// --------------------------------
using System;
using System.Collections.Generic;
using ExternalClasses.Sounds;
using FarseerPhysics.Collision.Shapes;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Contacts;
using FarseerPhysics.Factories;
using FITClub.Episode1.Configuration;
using FITClub.Episode1.Controller.AI;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Media;
using System.ComponentModel;

namespace FITClub.Episode1.Components
{
    #region GAME MODE SELECTORS

    /// <summary>
    /// Enumerable type - determines conditions under which the game ends
    /// </summary>
    public enum GameType
    {
        Time, Frags, Lives
    }

    /// <summary>
    /// Enumerable type - determines conditions of players ranks
    /// </summary>
    public enum PositionFactors { Kills, Lives }

    #endregion GAME MODE SELECTORS

    #region PLAYER'S STATISTICS

    /// <summary>
    /// This class keeps track of all player's statistics
    /// </summary>
    public class PlayerStats : INotifyPropertyChanged
    {

        public int Lives, Kills, Deaths, Position, KillStreak, BestKillStreak;
        public bool Eliminated, Dead;
        public double RespawnTime;
        public Vector2 NextRespawn;
        //public string Name;
        public PlayableCharacter Character;
        //private ModelPreset textures;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="game">Game this player's avatar belongs to</param>
        /// <param name="name">Name of the player</param>
        /// <param name="lives">Initial number of lives of this player</param>
        public PlayerStats(PlayableCharacter owner)
        {
            //this.Lives = lives;
            this.Position = 1;
            //this.Name = name;
            this.Dead = true;
            this.RespawnTime = 0;
            this.KillStreak = 0;
            this.Character = owner;
            this.NextRespawn = this.Character.Position;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public void FirePropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        
    }

    #endregion PLAYER'S STATISTICS

    #region RESPAWN POINT

    /// <summary>
    /// This class represents respawn point in the game - it detects avatars in respawn perimeter which disables it
    /// </summary>
    public class RespawnPoint
    {
        public Body Sensor;
        public List<Contact> Contacts;
        public bool Free = true;
        public Vector2 Position;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="game">Game this respawn point belongs to</param>
        /// <param name="position"></param>
        public RespawnPoint(GameFITClub game, Vector2 position)
        {
            this.Contacts = new List<Contact>();
            this.Sensor = BodyFactory.CreateCircle(game.PhysicsWorld, 0.5f, 1, "respawn");
            this.Sensor.SetTransform(position, 0);
            this.Sensor.BodyType = BodyType.Static;
            this.Sensor.IsSensor = true;
            this.Sensor.OnCollision += SensorOnCollision;
            this.Sensor.OnSeparation += SensorOnSeparation;
            this.Sensor.Enabled = true;
            this.Position = position;
        }

        /// <summary>
        /// This event is executed each time something collides with the respawn point
        /// </summary>
        public bool SensorOnCollision(Fixture f1, Fixture f2, Contact contact)
        {
            if (f2.ShapeType == ShapeType.Circle)
            {
                if (f2.Body.UserData != null)
                {
                    if ((f2.Body.UserData as string) == "powerup" || (f2.Body.UserData as string) == "fist") return true;
                }
                this.Contacts.Add(contact);
                this.Free = false;
            }
            return true;
        }

        /// <summary>
        /// This event is executed each time something stops colliding with the respawn point
        /// </summary>
        public void SensorOnSeparation(Fixture f1, Fixture f2)
        {
            foreach (Contact c in this.Contacts)
            {
                if ((c.FixtureA == f1 && c.FixtureB == f2) || (c.FixtureA == f2 && c.FixtureB == f1))
                {
                    this.Contacts.Remove(c);
                    if (this.Contacts.Count <= 0) this.Free = true;
                    return;
                }
            }
        }
    }

    #endregion RESPAWN POINT

    /// <summary>
    /// This class represents game component responsible for controling game progress
    /// </summary>
    public class GameStateManager : Microsoft.Xna.Framework.GameComponent
    {
        #region FIELDS

        /// <summary>
        /// List of statistics of each player in the game
        /// </summary>
        public List<PlayerStats> Statistics;
        //public EndGameFactors EndGameFactor;
        public PositionFactors PositionFactor;
        private TimeSpan remainingTime;
        public bool GameOver { get; private set; }
        /// <summary>
        /// This property sets or gets Elapsed/remaining time for the game
        /// </summary>
        public TimeSpan RemainingTime
        {
            get { return remainingTime; }
            set
            {
                this.remainingTime = value;
                if (this.CountDown)
                {
                    if (remainingTime < TimeSpan.FromSeconds(30))
                    {
                        this.game.Hud.Time = string.Format("{0:00}.{1:0}", this.remainingTime.Seconds, (int)(this.remainingTime.Milliseconds / 100));
                    }
                    else
                    {
                        this.game.Hud.Time = string.Format("{0:00}:{1:00}", (int)this.remainingTime.TotalMinutes, this.remainingTime.Seconds);
                    }
                }
                else
                {
                    this.game.Hud.Time = string.Format("{0:00}:{1:00}", (int)this.remainingTime.TotalMinutes, this.remainingTime.Seconds);
                }
            }
        }

        private GameFITClub game;
        /// <summary>
        /// This determines game mode
        /// </summary>
        private GameType type;
        /// <summary>
        /// This sets maximum number of kill before the game ends if specific game mode is selected
        /// </summary>
        public int MaxKills;
        private int cntdn;
        private bool CountDown;
        private PseudoRandom rnd;
        private ModelPreset powerUpTextures;
        private List<RespawnPoint> RespawnPoints;
        ///private List<Vector2> AvaiableRespawnPoints;
        private Song cnk;
        public bool FirstBlood, cntd;

        #endregion FIELDS

        #region METHODS

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="game">Game instance</param>
        /// <param name="type">Game mode</param>
        /// <param name="positionFactor">Ranking system</param>
        /// <param name="time">Remaining time</param>
        /// <param name="maxKills">Max. nuber of kills</param>
        /// <param name="InitLives">Initial number of lives</param>
        public GameStateManager(GameFITClub game, GameType type, PositionFactors positionFactor, TimeSpan time, int maxKills, int InitLives)
            : base(game)
        {
            this.game = game;
            this.type = type;
            this.PositionFactor = positionFactor;
            this.RespawnPoints = new List<RespawnPoint>();
            for (int i = 0; i < game.ActualLevel.Config.Respawns.Count; i++)
            {
                this.RespawnPoints.Add(new RespawnPoint(game, game.ActualLevel.Config.Respawns[i]));
            }
            this.Statistics = new List<PlayerStats>();
            rnd = new PseudoRandom();//new Random((int)DateTime.UtcNow.Ticks);

            foreach (var item in game.characters.Values)
            {
                this.Statistics.Add(item.Statistics);
                item.Statistics.Lives = InitLives;
            }
            
            if (this.type == GameType.Time)
            {
                this.CountDown = true;
                this.remainingTime = time;
                //string[] ttt = game.ListLevelTime.SelectedValue.Split(':');
                //this.remainingTime = TimeSpan.FromMinutes(double.Parse(ttt[0])) + TimeSpan.FromSeconds(double.Parse(ttt[1]));
            }
            else
            {
                this.remainingTime = new TimeSpan(0, 0, 0);
                if (this.type == GameType.Frags)
                {
                    if (maxKills > 0) this.MaxKills = maxKills;
                    else this.MaxKills = 10;
                }
            }
            this.cnk = this.game.Content.Load<Song>("Music\\wtrts");
            powerUpTextures = this.game.ModelPresets["PowerUps"];

            this.game.SoundPlayer.PlaySoundEffect("Prepare", Vector2.Zero);
            this.cntdn = 5;
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            if (this.CountDown)
            {
                this.RemainingTime -= gameTime.ElapsedGameTime;
                if (this.remainingTime.TotalSeconds <= 5.9 && this.cntdn == 5)
                {
                    game.SoundPlayer.PlaySoundEffect("5", Vector2.Zero);
                    this.cntdn--;
                }
                if (this.remainingTime.TotalSeconds <= 4.9 && this.cntdn == 4)
                {
                    game.SoundPlayer.PlaySoundEffect("4", Vector2.Zero);
                    this.cntdn--;
                }
                if (this.remainingTime.TotalSeconds <= 3.9 && this.cntdn == 3)
                {
                    game.SoundPlayer.PlaySoundEffect("3", Vector2.Zero);
                    this.cntdn--;
                }
                if (this.remainingTime.TotalSeconds <= 2.9 && this.cntdn == 2)
                {
                    game.SoundPlayer.PlaySoundEffect("2", Vector2.Zero);
                    this.cntdn--;
                }
                if (this.remainingTime.TotalSeconds <= 1.9 && this.cntdn == 1)
                {
                    game.SoundPlayer.PlaySoundEffect("1", Vector2.Zero);
                    this.cntdn--;
                }
                if (this.remainingTime.TotalSeconds <= 0.9 && this.cntdn == 0)
                {
                    game.SoundPlayer.PlaySoundEffect("0", Vector2.Zero);
                    this.cntdn--;
                }
                if (this.remainingTime <= TimeSpan.FromSeconds(0))
                {
                    /////// end game
                    this.GameOver = true;
                    this.game.EndGame();
                    return;
                }
            }
            else
            {
                this.RemainingTime += gameTime.ElapsedGameTime;
            }

            bool doCompare = false;
            for (int i = 0; i < game.PlayerCount; i++)
            {
                if (!Statistics[i].Eliminated)
                {
                    if (Statistics[i].Dead)
                    {
                        if (Statistics[i].RespawnTime > 0) Statistics[i].RespawnTime -= gameTime.ElapsedGameTime.TotalSeconds;
                        if (Statistics[i].RespawnTime < 0) Statistics[i].RespawnTime = 0;
                        if (Statistics[i].RespawnTime == 0)
                        {
                            Respawn(i);
                        }
                    }
                    else if (Statistics[i].Character.Position.Y < this.game.ActualLevel.Fluid.Config.Position.Y)
                    {
                        Kill(i);
                        this.game.SoundPlayer.PlaySoundEffect("Splash", Statistics[i].Character.Position);
                        if (!this.FirstBlood)
                        {
                            this.FirstBlood = true;
                            this.game.SoundPlayer.PlaySoundEffect("FirstBlood", Vector2.Zero);
                        }
                        doCompare = true;
                    }
                    ////////////// update texture
                    this.game.Hud.PowerupImages[i].Texture = powerUpTextures.Textures[(int)Statistics[i].Character.Actions.Status.PowerUpEffect].Texture;
                }
            }
            if (doCompare) Compare();
            TestEnd();

            base.Update(gameTime);
        }

        /// <summary>
        /// Getter, grants access to current type of game
        /// </summary>
        /// <returns>GameType Enum</returns>
        public GameType getGameType()
        {
            return this.type;
        }

        /// <summary>
        /// Respawns player back to the game
        /// </summary>
        /// <param name="i">Player index</param>
        private void Respawn(int i)
        {
            Statistics[i].Character.Respawn(Statistics[i].NextRespawn);
            Statistics[i].Dead = false;
            Statistics[i].Character.Enable();
#if DEBUG
            //Console.WriteLine(Statistics[i].Name + " was respawnd");
#endif
        }

        /// <summary>
        /// Kills the player and disables its avatar
        /// </summary>
        /// <param name="i">Player index</param>
        public void Kill(int i)
        {
            Kill(this.Statistics[i], this.Statistics[i].Character.LastContactWith);            
        }

        public void Kill(PlayerStats stats, PlayableCharacter killedBy)
        {
            stats.Character.Visible = false;

            stats.Dead = true;
            stats.KillStreak = 0;
            stats.Deaths++;
            stats.Lives--;
            stats.FirePropertyChanged("Lives;Deaths");
            stats.Character.AnimPlayer.StopTake("Crouch_in");
            stats.Character.AnimPlayer.StopTake("Jump_in");
            stats.Character.AnimPlayer.StopTake("Get_Hit_Bottom");
            stats.Character.CharPhysx.StandUp();
            stats.Character.AnimPlayer.StartTake("Idle");
            if (this.type == GameType.Lives)
            {
                if (stats.Lives <= 0)
                {
                    stats.Eliminated = true;
#if DEBUG
                    Console.WriteLine(stats.Name + " was eliminated");
#endif
                    int elim = 0;
                    for (int j = 0; j < this.Statistics.Count; j++)
                    {
                        if (this.Statistics[j].Eliminated && this.Statistics[j] != stats) elim++;
                    }
                    stats.Position = this.Statistics.Count - elim;
                }
            }
            stats.RespawnTime = 2.0;
            //stats.Player.Disable();
            if (killedBy != null)
            {
#if DEBUG
                Console.WriteLine(stats.Name + " was killed by " + stats.Character.LastContactWith.Name);
#endif
                killedBy.Statistics.Kills++;
                killedBy.Statistics.KillStreak++;
                stats.FirePropertyChanged("Kills;Streak");
                CheckKillStreak(killedBy.Statistics);
            }
            else
            {
#if DEBUG
                Console.WriteLine(stats.Name + " was killed by his own stupidity");
#endif
            }
            stats.NextRespawn = this.ChooseRespawn();
            stats.Character.FireKilled();
        }

        private Vector2 ChooseRespawn()
        {
            Vector2 v;
            try
            {
                int pos = rnd.GetInt32(this.RespawnPoints.Count);//rnd.Next(this.AvaiableRespawnPoints.Count);
                v = this.RespawnPoints[pos].Position;
            }
            catch (Exception)
            {
                v = new Vector2(0, 0);
            }
            return v;
        }

        /// <summary>
        /// Check if player reached a killstreak and reacts accordingly
        /// </summary>
        /// <param name="stats">Player's stats</param>
        public void CheckKillStreak(PlayerStats stats)
        {
            if (stats.BestKillStreak < stats.KillStreak) stats.BestKillStreak = stats.KillStreak;
            if (stats.Character.Actions.Status.PowerUpEffect == Levels.PowerUpType.Invisibility) return;
            switch (stats.KillStreak)
            {
                case 1:
                    break;
                case 2:
                    this.game.SoundPlayer.PlaySoundEffect("DoubleKill", stats.Character.Position);
                    stats.Character.PopUp.SetText("DOUBLE KILL");
                    break;
                case 3:
                    this.game.SoundPlayer.PlaySoundEffect("TripleKill", stats.Character.Position);
                    stats.Character.PopUp.SetText("TRIPPLE KILL", Color.DarkBlue);
                    break;
                case 4:
                    this.game.SoundPlayer.PlaySoundEffect("MultiKill", stats.Character.Position);
                    stats.Character.PopUp.SetText("MULTI KILL", Color.Violet);
                    break;
                case 5:
                    this.game.SoundPlayer.PlaySoundEffect("UltraKill", stats.Character.Position);
                    stats.Character.PopUp.SetText("ULTRA KILL", Color.DarkGoldenrod);
                    break;
                case 6:
                    this.game.SoundPlayer.PlaySoundEffect("MonsterKill", stats.Character.Position);
                    stats.Character.PopUp.SetText("MONSTER KILL", Color.Orange);
                    break;
                case 7:
                    this.game.SoundPlayer.PlaySoundEffect("Rampage", stats.Character.Position);
                    stats.Character.PopUp.SetText("RAMPAGE", Color.OrangeRed);
                    break;
                case 8:
                    MusicPlayer.InterruptWithThisSong(cnk);
                    stats.Character.PopUp.SetText("CHUCK NORRIS KILL", Color.Red);
                    break;
                default:
                    stats.Character.PopUp.SetText("CHUCK NORRIS KILL " + (stats.KillStreak - 7), Color.Red);
                    break;
            }
        }

        /// <summary>
        /// Compare statistics of all players and determines their positions according to the selected ranking system
        /// </summary>
        public void Compare()
        {
            if (PositionFactor == PositionFactors.Kills)
            {
                List<int> sorter = new List<int>();
                for (int i = 0; i < Statistics.Count; i++) if (!sorter.Contains(Statistics[i].Kills)) sorter.Add(Statistics[i].Kills);
                sorter.Sort();
                sorter.Reverse();
                for (int i = 0; i < sorter.Count; i++)
                {
                    for (int j = 0; j < Statistics.Count; j++)
                    {
                        if (Statistics[j].Kills == sorter[i])
                        {
                            Statistics[j].Position = i + 1;
                            //Console.WriteLine(Statistics[j].Name + " is on {0}. place", Statistics[j].Position);
                        }
                    }
                }
            }
            else
            {
                List<int> sorter = new List<int>();
                for (int i = 0; i < Statistics.Count; i++) if (!sorter.Contains(Statistics[i].Lives) && !Statistics[i].Eliminated) sorter.Add(Statistics[i].Lives);
                sorter.Sort();
                sorter.Reverse();
                for (int i = 0; i < sorter.Count; i++)
                {
                    for (int j = 0; j < Statistics.Count; j++)
                    {
                        if (Statistics[j].Lives == sorter[i] && !Statistics[i].Eliminated)
                        {
                            Statistics[j].Position = i + 1;
#if DEBUG
                            Console.WriteLine(Statistics[j].Name + " is on {0}. place", Statistics[j].Position);
#endif
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Test if the game is supposed to end
        /// </summary>
        public void TestEnd()
        {
            if (this.type == GameType.Frags)
            {
                for (int i = 0; i < this.Statistics.Count; i++)
                {
                    if (this.Statistics[i].Kills >= this.MaxKills)
                    {
                        this.Compare();
                        this.GameOver = true;
                        this.game.EndGame();
                        return;
                    }
                }
            }
            else if (this.type == GameType.Lives)
            {
                int elim = this.Statistics.Count;
                for (int j = 0; j < this.Statistics.Count; j++)
                {
                    if (this.Statistics[j].Eliminated) elim--;
                }
                if (elim == 1)
                {
                    this.GameOver = true;
                    this.game.EndGame();
                    return;
                }
            }
        }

        #endregion METHODS
    }
}